package org.molamil.acto.ext.perilla {
	/**
	 * @author martindyrby
	 */

	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.external.ExternalInterface;
	import flash.utils.describeType;
	import flash.utils.getQualifiedClassName;


	public class Perilla {
		private var rootObj: *;
		private var typeDefinitionCallback : String;
		private var removeDefinitionCallback : String;
		private var editPropertyCallback : String;
		private var valueCallback : String;
		static private var logCallback : Function;
		private var objectIdCallback : String;
		
		private var registeredObjects:Object;
		
		private static var instance : Perilla;
		
		public static function init(root : *) : void {
			if(instance != null) return; 
			instance = new Perilla();

			privateInit(root);
		}
		
		private static function privateInit(root:*):void {
			instance.rootObj = root;
			instance.registeredObjects = new Object();
			instance.setupOutboundCommunication();
			instance.setupInboundCommunication();
			instance.sendObjectId();			
		}
		public static function register(name:String, target : Object) : void {
			instance.registeredObjects[name] = {target: target, propertyName: ""};
			instance.sendTypeDef(target, name);
		}
		
		public static function unregister(name:String) : void {
			instance.sendRemoveTypeDef(name);
		}

		private static function createId() : String {
			return "reg"+Math.random()*500;
		}
		
		public static function set logFunction(value:Function):void {
			logCallback = value;

		}

		private function initAfterTimeout() : void {

		}

		private function setupInboundCommunication() : void {
			if(ExternalInterface.available) {
				ExternalInterface.addCallback("changeNumeric", onChangeNumeric);
				log("listen for changeNumeric");
				ExternalInterface.addCallback("setSimple", onSetSimple);
				log("listen for onSetSimple");
				ExternalInterface.addCallback("setBoolean", onSetBoolean);
				log("listen for onSetBoolean");
				ExternalInterface.addCallback("addNumeric", onAddNumeric);
				log("listen for addNumeric");
				ExternalInterface.addCallback("changeString", onChangeString);
				log("listen for changeString");
				ExternalInterface.addCallback("requestTypeDef", onRequestTypeDef);
				log("listen for requestTypeDef");
				ExternalInterface.addCallback("requestValue", onRequestValue);
				log("listen for requestValue");
				ExternalInterface.addCallback("executeMethod", onMethod);
				log("listen for executeMethod");
				ExternalInterface.addCallback("executeBooleanMethod", onBooleanMethod);
				log("listen for executeBooleanMethod");
				ExternalInterface.addCallback("executeMethodWithParams", onParamMethod);
				log("listen for executeMethodWithParams");
			}
		}

		private function onRequestTypeDef(path:String) : void {
			log('onRequestTypeDef: ' + (onRequestTypeDef));
			var obj : * = translatePath(path);
			log('obj: ' + (obj));

 			sendTypeDef(obj, path);
		}

		private function log(string : String) : void {
			if(logCallback != null) logCallback.apply(null, [string]);
		}
		
		private function onRequestValue(path:String):void{
			var value:* = translatePath(path);
			log(value);
			if(ExternalInterface.available) {
				log(valueCallback+","+value+","+path);
				ExternalInterface.call(valueCallback, value, path);
			}
		}

		private function sendTypeDef(obj : *, path:String) : void {
			if(ExternalInterface.available) {
				var type:XML = describeType(obj);
				
				type.@path = path;
				log('__type.@path: ' + (type.@path));
				
				if(obj is DisplayObjectContainer){
					addChildren(type,obj);
				}
				log(typeDefinitionCallback);

				ExternalInterface.call(typeDefinitionCallback, type.toXMLString());
				
			}
		}
		
		private function sendRemoveTypeDef(name:String):void {
			if(ExternalInterface.available) ExternalInterface.call(removeDefinitionCallback, name);
		}
		
		private function sendEditProperty(id : String, target : Object, propertyName : String) : void {
			if(ExternalInterface.available) {
				var type:XML = describeType(target);
				
				type.@path = id;			
				log("sendEditProperty("+id+","+target+", "+propertyName +")");
				ExternalInterface.call(editPropertyCallback, propertyName, type.toXMLString());
			}
		}


		
		private function sendObjectId():void {
			if(ExternalInterface.available) {
				log("ExternalInterface.objectID: "+ExternalInterface.objectID);
				ExternalInterface.call(objectIdCallback, ExternalInterface.objectID);
			}
		}

		private function addChildren(type : XML, obj : DisplayObjectContainer) : void {
			var numChildren:int = obj.numChildren;

			for (var i : int = 0; i < numChildren; ++i){
				var child:DisplayObject = obj.getChildAt(i);
				var xml:XML = new XML('<child name="'+child.name+'" type="'+getQualifiedClassName(child)+'" />');
				type.appendChild(xml);
			}
		}

		private function onSetSimple(path:String, property:String, value:String) : void {
			var obj : * = translatePath(path);
			obj[property] = value;
			sendCurrentValue(path, property, obj[property]);
		}
		
		private function onSetBoolean(path:String, property:String, value:String) : void {
			var obj : * = translatePath(path);
			obj[property] = value.toLowerCase() == "true" || value == "1";
			sendCurrentValue(path, property, obj[property]);
		}
		
		private function onChangeString(path:String, property:String, value:String) : void {
			var obj : * = translatePath(path);
			obj[property] = value;
			sendCurrentValue(path, property, obj[property]);
		}
				
		private function onChangeNumeric(path:String, property:String, value:String) : void {
			var obj : * = translatePath(path);
			obj[property] = Number(value);
			sendCurrentValue(path, property, obj[property]);
		}
		
		private function onAddNumeric(path:String, property:String, value:String) : void {
			var obj : * = translatePath(path);
			obj[property] += Number(value);
			sendCurrentValue(path, property, obj[property]);
		}
		
		private function onMethod(path:String, method:String) : void {
			var obj : * = translatePath(path);
			(obj[method] as Function).apply();
		}
		
		private function onBooleanMethod(path:String, method:String, value:String) : void {
			var obj : * = translatePath(path);
			var bool:Boolean = value == "true";
			(obj[method] as Function).apply(null, [bool]);
		}
		
		private function onParamMethod(path:String, method:String, ...params) : void {
			var obj : * = translatePath(path);
			(obj[method] as Function).apply(null, params);
		}
		

		private function translatePath(path : String) : * {
			log("translatePath: "+path);
			var elements:Array = path.split(".");
			
			var obj:Object;
			if(registeredObjects.hasOwnProperty(elements[0])) {
				var regPath:String = elements.shift();
				log("regPath: "+regPath);
				obj = registeredObjects[regPath].target;
				log("obj: "+obj);
			} else {
				obj = rootObj; 
			}

			var nextPart:String;
			var dcObj:DisplayObjectContainer;
			var nextChild:DisplayObject;
			log("elements.length: "+elements.length);
			for (var i : int = 0; i < elements.length; ++i) {
				nextPart = elements[i];
				log("nextPart: "+nextPart);

				if (nextPart != '') {

					if (obj.hasOwnProperty(nextPart)){
						log("obj.hasOwnProperty(nextPart)");
						obj = obj[nextPart];
						continue;
					} else if(obj is DisplayObjectContainer){
						dcObj = DisplayObjectContainer(obj);
						nextChild = dcObj.getChildByName(nextPart);
						if (nextChild != null) {
							obj = nextChild;
							continue;
						} else {
							for(var j:int = 0; j < dcObj.numChildren;++j){
								nextChild = dcObj.getChildAt(j);
								if (nextChild.name == nextPart) {
									obj = nextChild;
									break;
								}
							}
							continue;
						}
					}
				}
			}
			
			return obj;
		}
		
		
		private function sendCurrentValue(path : String, property : String, value : *) : void {
			if(ExternalInterface.available) {
				ExternalInterface.call(valueCallback, value, "."+path+"."+property);
			}
		}

		private function setupOutboundCommunication() : void {
			typeDefinitionCallback = "window.flashController.addTypeDef";
			removeDefinitionCallback = "window.flashController.removeTypeDef";
			valueCallback = "window.flashController.asValueforPath";
			objectIdCallback = "window.flashController.asObjectId";
			editPropertyCallback = "window.flashController.asEditPropForType";
		}
	}
}
